package webapi.controller;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.headers.Header;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.ExampleObject;
import io.swagger.v3.oas.annotations.parameters.RequestBody;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import webapi.storage.ReadOnlyDataChunk;
import webapi.storage.StorageService;
import webapi.storage.WriteOnlyDataChunk;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URLEncoder;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.util.UUID;

@Controller
@RequestMapping("/data")
public class DataController {
    private final Logger logger = LoggerFactory.getLogger(DataController.class);
    private final StorageService storageService;

    public DataController(StorageService storageService) {
        this.storageService = storageService;
    }

    @Operation(summary = "数据块上传接口", description =
            "以纯文本(text/plain)或以字节流(application/octet-stream)形式发送数据块")
    @RequestBody(
            description = "原始数据块",
            content = {
                    @Content(
                            mediaType = "application/octet-stream",
                            examples = {
                                    @ExampleObject(description = "二进制数据块"),
                            }
                    ),
                    @Content(
                            mediaType = "text/plain;charset=UTF-8",
                            examples = {
                                    @ExampleObject(value = "Hello world", description = "纯文本数据块, 字符集=UTF-8"),
                            }
                    ),
            }
    )
    @ApiResponses(value = {
            @ApiResponse(responseCode = "413", description = "413 PAYLOAD_TOO_LARGE: 上传数据块总字节数已超出允许值上限(暂定16M字节)"),
            @ApiResponse(responseCode = "400", description = "400 BAD_REQUEST: 请求参数无效"),
            @ApiResponse(
                    responseCode = "201", description = "201 CREATED: 成功创建数据块, 请通过应答包头中的 Location 相对路径下载",
                    headers = {
                            @Header(name = "Location", description = "数据块位置", required = true),
                    }
            ),
    })
    @PostMapping("/chunkAdd")
    public ResponseEntity<Void> addDataChunk(
            HttpServletRequest request,
            @RequestParam(required = false, defaultValue = "") String extraOpt
            ) {
        String contentType = request.getContentType();
        logger.debug("数据块格式: {}", contentType);
        logger.debug("用户自定义附加选项: {}", extraOpt);

        final long total = request.getContentLengthLong();
        final int PAYLOAD_SIZE_MAX = 16*1024*1024 - 1;
        if (total > PAYLOAD_SIZE_MAX) {
            return ResponseEntity.status(HttpStatus.PAYLOAD_TOO_LARGE).build();
        }
        logger.debug("数据总字节数: {}", total);

        if (!storageService.serviceIsOnline()) {
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
        }

        UUID uuid = UUID.randomUUID();
        URI location = URI.create("/data/chunks/" + uuid.toString());
        try (
                WriteOnlyDataChunk chunk =
                        storageService.createChunkAndOpenWriteOnly(uuid.toString());
                ReadableByteChannel inputDataChannel =
                        Channels.newChannel(
                                request.getInputStream()
                        )
        ){
            long transferredBytes = chunk.transferFrom(inputDataChannel, 0, total);
            logger.debug("文件名=chunks/{}, transferredBytes={}", uuid, transferredBytes);
            return ResponseEntity.created(location).build();
        } catch (IOException ioException) {
            logger.warn("无法读取http数据流: {}", ioException.getMessage());
            return ResponseEntity.badRequest().build();
        }
    }

    @ApiResponses(value = {
            @ApiResponse(responseCode = "404", description = "数据块不存在"),
            @ApiResponse(responseCode = "200", description = "OK"),
    })
    @GetMapping(value = "/chunks/{chunkUuid}", produces = "application/octet-stream")
    public ResponseEntity<?> getDataChunk(@PathVariable String chunkUuid) {
        //设置响应头
        HttpHeaders headers = new HttpHeaders();
        String fileName = chunkUuid + ".dat";
        try {
            String encodedFileName = URLEncoder.encode(fileName, "UTF-8");
            headers.add("Content-Disposition", String.format(
                    "attachment; filename*=UTF-8''%s; filename=\"%s\"", encodedFileName, encodedFileName));
        } catch (UnsupportedEncodingException ignored) {
            headers.add("Content-Disposition", "attachment; filename=\"file.dat\"");
        }
        headers.add("Cache-Control", "no-cache, no-store, must-revalidate");
        headers.add("Pragma", "no-cache");
        headers.add("Expires", "0");
        try (ReadOnlyDataChunk chunk = storageService.openChunkReadOnly(chunkUuid)) {
            Resource resource = chunk.getResource();
            return ResponseEntity.ok()
                    .headers(headers)
                    .contentType(MediaType.APPLICATION_OCTET_STREAM)
                    .contentLength(resource.contentLength())
                    .body(resource);
        } catch (IOException ioException) {
            return ResponseEntity.notFound().build();
        }
    }
}
